package org.yaac.server.egql.evaluator;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Sets.newHashSet;

import java.util.Set;

import org.yaac.server.egql.EntityProperty;
import org.yaac.server.egql.evaluator.aggregator.Aggregator;
import org.yaac.server.egql.evaluator.aggregator.AvgAggregator;
import org.yaac.server.egql.evaluator.aggregator.CountAggregator;
import org.yaac.server.egql.evaluator.aggregator.MaxAggregator;
import org.yaac.server.egql.evaluator.aggregator.MinAggregator;
import org.yaac.server.egql.evaluator.aggregator.SumAggregator;
import org.yaac.server.egql.exception.EGQLE004Exception;
import org.yaac.server.egql.exception.EGQLException;
import org.yaac.server.egql.processor.ProcessData.ProcessDataRecord;




/**
 * @author Max Zhu (thebbsky@gmail.com)
 *
 */
public class AggregationEvaluator extends Evaluator {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * aggregation types
	 * 
	 * @author Max Zhu (thebbsky@gmail.com)
	 *
	 */
	public enum Type {
		COUNT(CountAggregator.class), 
		SUM(SumAggregator.class), 
		AVG(AvgAggregator.class), 
		MAX(MaxAggregator.class), 
		MIN(MinAggregator.class);
		
		Class<? extends Aggregator> clazz;

		private Type(Class<? extends Aggregator> clazz) {
			this.clazz = clazz;
		}
		
		public Aggregator newAggregator() {
			try {
				return this.clazz.newInstance();
			} catch (InstantiationException e) {
				// should not happen, as covered by test case
				e.printStackTrace();
				return null;
			} catch (IllegalAccessException e) {
				// should not happen, as covered by test case
				e.printStackTrace();
				return null;
			}
		}
	}
	
	private final Evaluator op;
	
	private Type type;
	
	public AggregationEvaluator(String type, Evaluator op) {
		checkArgument(!isNullOrEmpty(type), "Aggregation name is mandatory");
		
		for (Type t : Type.values()) {
			if (t.toString().equals(type.toUpperCase())) {
				this.type = t;
				break;
			}
		}
		
		checkNotNull(this.type, "Invalid aggregation name : " + type);
		
		this.op = op;
	}
	
	public Type getType() {
		return type;
	}
	
	@Override
	public EvaluationResult evaluate(ProcessDataRecord record) {
		return record.lookup(this.getText());
	}

	public void aggregate(ProcessDataRecord record, Aggregator agg) {
		EvaluationResult result = op.evaluate(record);
		agg.aggregate(result);
	}
	
	/**
	 * init new aggregator with emtpy value
	 * 
	 * @param context
	 * @return
	 */
	@Deprecated
	public Aggregator newAggregator() {		
		switch (this.type) {
		case COUNT:
			// COUNT will only count those records in which the field in the brackets is NOT NULL.
			return new CountAggregator();
		case MAX:
			return new MaxAggregator();
		case MIN:
			return new MinAggregator();
		case AVG:
			return new AvgAggregator();
		case SUM:
			return new SumAggregator();
		default:
			throw new IllegalArgumentException("Unsupported aggregation aggregation type : " + this.type);
		}
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((op == null) ? 0 : op.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		AggregationEvaluator other = (AggregationEvaluator) obj;
		if (op == null) {
			if (other.op != null)
				return false;
		} else if (!op.equals(other.op))
			return false;
		if (type != other.type)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "AggregationEvaluator [op=" + op + ", type=" + type + "]";
	}

	@Override
	public Set<AggregationEvaluator> aggregationChildren() {
		// it is possible that aggregation function is nested
		Set<AggregationEvaluator> result = newHashSet(op.aggregationChildren());
		result.add(this);
		return result;
	}
	
	@Override
	public Set<EntityProperty> nonAggregationProperties() {
		return newHashSet();
	}

	@Override
	public void validate() throws EGQLException {
		if (!op.aggregationChildren().isEmpty()) {
			// at the moment Yaac does not support nested aggregation function
			throw new EGQLE004Exception();
		}
	}
}
